﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace ru.yourpictures.Core.Imaging.Filters
{
    public enum SimpleColor : int
    {
        Blue = 0,
        Green = 1,
        Red = 2,
        Alpha = 3
    }

    public abstract class FilterBase : IFilter, IDisposable
    {
        /// <summary>
        /// Default pixel format. It's very important for low-level filters, since
        /// some of them work with 24bpp format only.
        /// </summary>
        public static PixelFormat DefaultPixelFormat = PixelFormat.Format24bppRgb;

        /// <summary>
        /// This method has to be implemented in each concrete filter
        /// </summary>
        protected abstract void FilterImplementation();

        #region Public Properties
        public System.Drawing.Color BackGroundColor
        {
            get{return _backColor;}
            set{_backColor = value;}
        }

        public InterpolationMode InterpolationMode
        {
            get { return _interpolationMode; }
            set { _interpolationMode = value; }
        }
        #endregion

        #region Private fields
        private bool disposed = false;

        protected InterpolationMode _interpolationMode = InterpolationMode.HighQualityBicubic;

        /// <summary>Default is a transparent background</summary>
        protected System.Drawing.Color _backColor = System.Drawing.Color.FromArgb(0, 0, 0, 0);

        /// <summary>Pointer to source bitmap</summary>
        protected Bitmap _b = null;

        /// <summary>Pointer to Bitmap first byte in memory</summary>
        protected IntPtr _ptr;

        /// <summary>Array of pixels</summary>
        protected byte[] _pixels = null;

        /// <summary>Total count of bytes in image</summary>
        protected int _size = 0;

        /// <summary>How many bytes each pixel takes</summary>
        protected int _bytesPerPixel = 0;

        /// <summary>Bitmap data</summary>
        protected BitmapData _bmpData = null;

        /// <summary>Stride (bytes per row)</summary>
        protected int _stride = 0;

        /// <summary>Width of bitmap (in pixels)</summary>
        protected int _width = 0;

        /// <summary>Height of bitmap in pixels</summary>
        protected int _height = 0;

        /// <summary>Pixel format for currently processing bitmap</summary>
        protected PixelFormat _pixelFormat = PixelFormat.Undefined;
        #endregion

        #region PreProcessing
        /// <summary>
        /// Copies data from bitmap object to array of bytes.
        /// Also calculates some parameters
        /// </summary>
        /// <param name="b"></param>
        protected virtual void PreProcessing(Bitmap b)
        {
            _b = b;

            _pixelFormat = b.PixelFormat;

            // Lock bitmap bits
            _bmpData = b.LockBits(
                new Rectangle(0, 0, b.Width, b.Height),
                ImageLockMode.ReadWrite, b.PixelFormat);

            // Pointer to Bitmap first byte in memory
            _ptr = _bmpData.Scan0;

            // Stride
            _stride = _bmpData.Stride;

            // Total count of bytes in image
            _size = _stride * b.Height;

            // How many bytes each pixel takes
            _bytesPerPixel = Image.GetPixelFormatSize(b.PixelFormat) / 8;

            // Create memory arry
            _pixels = new byte[_size];

            _width = b.Width;

            _height = b.Height;

            // Copy data from image into memory array
            System.Runtime.InteropServices.Marshal.Copy(_ptr, _pixels, 0, _size);
        }

        #endregion

        #region PostProcessing
        protected virtual void PostProcessing(Bitmap b)
        {
            // Copy the RGB values back to the bitmap
            System.Runtime.InteropServices.Marshal.Copy(_pixels, 0, _ptr, _size);

            // Unlock previously locked bits
            b.UnlockBits(_bmpData);
        }

        #endregion

        #region IFilter Members
        #region Apply
        public virtual void Apply(Bitmap b)
        {
            try
            {
                PreProcessing(b);
                FilterImplementation();
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                PostProcessing(b);
            }
        }
        #endregion

        public virtual Bitmap Convert(Bitmap b)
        {
            Bitmap bmp = new Bitmap(b);
            Apply(bmp);
            return bmp;
        }
        #endregion

        public virtual void Apply(_BitmapInfo b)
        {
            throw new NotImplementedException("GDI+ mode is not implemented");
        }

        #region Static Utils
        public static void CopyPixel(int srcIndex, byte[] src, int destIndex, byte[] dest, int bytesPerPixel)
        {
            for (int i = 0; i < bytesPerPixel; i++)
                dest[i + destIndex] = src[i + srcIndex];
        }

        public static byte[] CopyArray(byte[] src)
        {
            byte[] ret = new byte[src.Length];
            src.CopyTo(ret, 0);
            return ret;
        }
        #endregion

        public System.Drawing.Color GetPixel(int x, int y)
        {
            int index = y * _stride + x * _bytesPerPixel;
            if (_pixelFormat == PixelFormat.Format24bppRgb ||
                _pixelFormat == PixelFormat.Format32bppRgb)
            {
                return System.Drawing.Color.FromArgb(_pixels[index + 2], _pixels[index + 1], _pixels[index]);
            }
            else if (_pixelFormat == PixelFormat.Format32bppArgb)
            {
                return System.Drawing.Color.FromArgb(_pixels[index + 3], _pixels[index + 2], _pixels[index + 1], _pixels[index]);
            }
            else if (_pixelFormat == PixelFormat.Format16bppArgb1555)
            {
            }
            else if (_pixelFormat == PixelFormat.Format16bppGrayScale)
            {
            }
            else if (_pixelFormat == PixelFormat.Format16bppRgb555)
            {
            }
            else if (_pixelFormat == PixelFormat.Format16bppRgb565)
            {
            }

            return System.Drawing.Color.Empty;
        }

        public virtual void Save(Bitmap b, ImageFormat imageFormat, string fileName)
        {
            b.Save(fileName, imageFormat);
        }

        public virtual void Save(Image img, string fileName)
        {
            Bitmap bmp = new Bitmap(img);
            bmp.Save(fileName, img.RawFormat);
        }

        public virtual void Save(ImageFormat imageFormat, string fileName)
        {
            _b.Save(fileName, imageFormat);
        }

        #region Pixel Format conversion and validation
        public static void ConvertToSupportedFormat(Bitmap b)
        {
            // Convert image if needed
            if (!FormatSupported(b.PixelFormat))
            {
                Bitmap bm = new Bitmap(b.Width, b.Height, DefaultPixelFormat);
                Graphics gbm = Graphics.FromImage(bm);
                gbm.DrawImage(b, 0, 0);
            }
        }

        protected void ValidatePixelFormat(PixelFormat format)
        {
            if (format == PixelFormat.Format24bppRgb ||
                format == PixelFormat.Format32bppArgb ||
                format == PixelFormat.Format32bppRgb)
                return;

            string msg = string.Format("Pixels format {0} is not supported by current filter.", Enum.GetName(format.GetType(), format));
            throw new NotImplementedException(msg);
        }

        public static bool FormatSupported(PixelFormat format)
        {
            return (format == PixelFormat.Format24bppRgb ||
                format == PixelFormat.Format32bppArgb ||
                format == PixelFormat.Format32bppRgb);
        }

        public static bool IsGrayscale(Bitmap b)
        {
            if (b.PixelFormat != PixelFormat.Format8bppIndexed)
                return false;

            // check palette
            ColorPalette cp = b.Palette;
            System.Drawing.Color c;
            // init palette
            for (int i = 0; i < 256; i++)
            {
                c = cp.Entries[i];
                if ((c.R != i) || (c.G != i) || (c.B != i))
                {
                    return false;
                }
            }

            return true;
        }
        #endregion

        #region IDisposable Members
        ~FilterBase()
        {
        }
        /// <summary>
        /// Dispose the object
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // Take yourself off of the Finalization queue 
            // to prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Object disposing routine
        /// </summary>
        /// 
        /// <param name="disposing"><b>True</b> if the method is called from
        /// <see cref="AForge.Imaging.Blob.Dispose()"/> method, <b>false</b> if the method is called
        /// from destructor.</param>
        /// 
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // dispose managed resources
                    _b.Dispose();
                }
            }
        }
        #endregion

        #region InscribeImage
        public static Size InscribeImage(int origWidth, int origHeight, int maxThumbWidth, int maxThumbHeight)
        {
            Size result = new Size();
            if (origHeight <= 0 || origWidth <= 0 || maxThumbHeight <= 0 || maxThumbWidth <= 0)
                return result;

            double ratioWidth = (double)origWidth / (double)maxThumbWidth;
            double rationHeight = (double)origHeight / (double)maxThumbHeight;
            double ratio = 1;

            if (origWidth >= maxThumbWidth || origHeight >= maxThumbHeight)
                ratio = Math.Max(ratioWidth, rationHeight);
            else
                ratio = Math.Min(ratioWidth, rationHeight);

            result.Width = (int)(Math.Ceiling((double)origWidth / ratio));
            result.Height = (int)(Math.Ceiling((double)origHeight / ratio));

            if (result.Width > maxThumbWidth)
                result.Width = maxThumbWidth;
            if (result.Height > maxThumbHeight)
                result.Height = maxThumbHeight;

            return result;
        }
        #endregion
    }
}
